package com.flyan.danmuhunter.common.utils;

import java.util.*;

/**
 * 提供一些助手函数，因为这些函数的特殊性所以它们被设计为不符合驼峰命名以便和正常的函数做区分。
 * 这里面的函数全都是不依赖于第三方库的纯函数，即无论何时何地的给予相同的输入，那么一定会产生
 * 相同的输出，并且函数内部不会改变输入的参数。
 *
 * 纯函数意味着了你可以完全放心的去使用它们而无需担心将它们加入程序后会产生一些隐晦的bug且在
 * 程序调用后导致入参发生变化而导致后面发生一些奇怪的事情，因为你可以完全知道函数的意图且不会
 * 遭受到背叛。但使用时候也要注意这个特性，结果一定是在返回参数里，例如 _list_copy 拷贝一个
 * list 到另一个 list，传入的 src 和 dest 都不会被改变，别使用完成后认为是 dest 已经改动，
 *  * 而应使用新的返回值。
 *
 * @author Flyan
 * @version 1.0
 * @date 2022/4/8 1:01
 */
@SuppressWarnings("all")
public final class Helper {

    private Helper() {  }

    /**
     * like !boolean
     *
     * @param b the bool value
     * @return !b
     */
    public static boolean
    _not(boolean b) {
        return !b;
    }

    public static boolean
    _and(boolean ...bs) {
        for (boolean b : bs) {
            if(!b) {
                return false;
            }
        }
        return true;
    }

    public static boolean
    _or(boolean ...bs) {
        for (boolean b : bs) {
            if(b) {
                return true;
            }
        }
        return false;
    }

    public static boolean
    _equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

    public static boolean
    _not_equals(Object a, Object b) {
        return _not(_equals(a, b));
    }

    public static boolean
    _empty(Collection c) {
        return c == null || c.isEmpty();
    }

    public static boolean
    _not_empty(Collection c) {
        return !_empty(c);
    }

    public static int
    _length(Collection c) {
        return c == null ? 0 : c.size();
    }

    public static boolean
    _str_black(CharSequence cs) {
        int n;
        if (cs != null && (n = cs.length()) != 0) {
            for(int i = 0; i < n; ++i) {
                if (!Character.isWhitespace(cs.charAt(i))) {
                    return false;
                }
            }
        }

        return true;
    }

    public static boolean
    _str_not_black(CharSequence cs) {
        return !_str_black(cs);
    }

    public static boolean
    _null(Object o) {
        return o == null;
    }

    public static boolean
    _not_null(Object o) {
        return o != null;
    }

    public static boolean
    _all_null(Object ...os) {
        for (Object o : os) {
            if(o != null) {
                return false;
            }
        }
        return true;
    }

    public static boolean
    _all_not_null(Object ...os) {
        for (Object o : os) {
            if(o == null) {
                return false;
            }
        }
        return true;
    }

    public static String
    _require_str_not_empty(String x, String defaultValue) {
        return (x != null && _not(x.isEmpty())) ? x : defaultValue;
    }

    public static Boolean
    _require_not_null(Boolean x, Boolean defaultValue) {
        return x != null ? x : defaultValue;
    }

    public static Integer
    _require_not_null(Integer x, Integer defaultValue) {
        return x != null ? x : defaultValue;
    }

    public static Long
    _require_not_null(Long x, Long defaultValue) {
        return x != null ? x : defaultValue;
    }

    public static Object
    _require_not_null(Object x, Object defaultValue) {
        assert x.getClass() == defaultValue.getClass();
        return x != null ? x : defaultValue;
    }

    /**
     * (K, V)
     *
     * @param <K>
     * @param <V>
     */
    public static final class Pair<K, V> {

        private K k;
        private V v;

        public Pair(K k, V v) {
            this.k = k;
            this.v = v;
        }

        public K getK() {
            return k;
        }

        public V getV() {
            return v;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Pair<?, ?> pair = (Pair<?, ?>) o;
            return Objects.equals(k, pair.k) && Objects.equals(v, pair.v);
        }

        @Override
        public int hashCode() {
            return Objects.hash(k, v);
        }

        @Override
        public String toString() {
            return "("+ k + ", " + v + ')';
        }

    }

    /**
     * zip two list, return a list of ziped, new list's size is mininum of two list's size.
     * if try zip a null or empty list then return empty list that makes perfect sense.
     * this function was inspired by Functional-Programming-Languages.
     *
     * eg. _zip([1, 2, 3, 4, 5], ["One", "Two", "Three"]) -> [(1,"One"), (2, "Two"), (3, "Three")]
     *
     *
     * @param xs    a list to zip
     * @param ys    a list to zip
     * @return      list of ziped
     */
    public static <T, V> List<Pair<T, V>>
    _zip(List<T> xs, List<V> ys) {
        if(_null(xs) || _null(ys) || xs.isEmpty() || ys.isEmpty()) {
            return Collections.emptyList();
        }

        int n = Math.min(xs.size(), ys.size());
        List<Pair<T, V>> zip = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            zip.add(new Pair<>(xs.get(i), ys.get(i)));
        }

        return zip;
    }

    /**
     * take n element from give list.
     *
     * eg. _take(3, [1,2,3,4,5]) -> [1,2,3]
     *
     * @param n
     * @param xs
     * @param <T>
     * @return
     */
    public static <T> List<T>
    _take(int n, List<T> xs) {
        if(n <= 0 || _null(xs) || xs.isEmpty()) {
            return Collections.emptyList();
        }

        if(n >= xs.size()) {
            /* New list for pure function */
            return new ArrayList<>(xs);
        }

        return xs.subList(0, n);
    }

    /**
     * generate a list that has repeat elements.
     * eg. _repeat(7, "HaHa") -> ["HaHa", "HaHa", "HaHa", "HaHa", "HaHa", "HaHa", "HaHa"]
     *
     * @param n
     * @param x
     * @param <T>
     * @return
     */
    public static <T> List<T>
    _repeat(int n, T x) {
        if(n <= 0) {
            return Collections.emptyList();
        }

        List<T> ans = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            ans.add(x);
        }

        return ans;
    }

    /**
     * use give object as join point to generate new list.
     * eg. _join("->", ["Me", "Study", "Play"]) -> ["Me", "->", "Study", "->", "Play"]
     *
     * @param x     join object
     * @param xs
     * @param <T>
     * @return
     */
    public static <T> List<T>
    _join(T x, List<T> xs) {
        if(_empty(xs)) {
            return Collections.emptyList();
        }

        int n = xs.size();
        List<T> ans = new ArrayList<>(n * 2 - 1);
        for (int i = 0; i < n - 1; i++) {
            ans.add(xs.get(i));
            ans.add(x);
        }
        ans.add(xs.get(n - 1));

        return ans;
    }

    /**
     * use give word as join point to generate string.
     * eg. _str_join("->", ["Me", "Study", "Play"]) -> "Me->Study->Play"
     *
     * @param x     join word
     * @param xs
     * @return
     */
    public static String
    _join_to_str(String x, List<Object> xs) {
        if(_empty(xs)) {
            return "";
        }

        int n = xs.size();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < n - 1; i++) {
            builder.append(Objects.toString(xs.get(i))).append(x);
        }
        builder.append(xs.get(n - 1));

        return builder.toString();
    }

    /**
     * Copies a list from the specified source list, beginning at the specified position,
     * to the specified position of the destination list.
     * eg. _list_copy([2], 0, [1,3,3], 0, 1) -> [2,3,3]
     *
     * @param src   source list
     * @param ss    begin position of source list
     * @param dest  destination list
     * @param ds    begin position of destination list
     * @param n     length of copies
     * @return      new list of copied
     */
    public static List<?>
    _list_copy(List<?> src, int ss,
               List<?> dest, int ds,
               int n) {
        int sn = _length(src), dn = _length(dest);
        if(_or(ss < 0, ss + n > sn, ds < 0, ds + n > dn)) {
            return null;
        }

        List ans = new ArrayList(dest);
        if(_empty(src)) {
            return ans;
        }

        for (int i = 0; i < n; i++, ++ss, ++ds) {
            ans.set(ds, src.get(ss));
        }

        return ans;
    }

}
